


var get_trace = (function () {

    var ANON = '{anonymous}',

    do_chrome = function (e) {
        return e.stack.replace(/^.*?\n/, '')
                .replace(/^.*?\n/, '')
                .replace(/^.*?\n/, '')
                .replace(/^[^\(]+?[\n$]/gm, '')
                .replace(/^\s+at\s+/gm, '')
                .replace(/^Object.<anonymous>\s*\(/gm, ANON+'()@')
                .split('\n');
    },
    
    do_firefox = function (e) {
        return (e.stack.replace(/^.*?\n/, '')
                .replace(/(?:\n@:0)?\s+$/m, '')
                .replace(/^\(/gm, ANON + '(')
                .split('\n'));
    },
    
    // Opera 7.x and 8.x only!
    do_opera = function (e) {
        var _re_line = /Line\s+(\d+).*?script\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i,
            lines = e.message.split('\n'), 
            i, j, len;
        
        for (i = 4, j = 0, len = lines.length; i < len; i += 2)
            if (_re_line.test(lines[i]))
                lines[j++] = ((RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 
                                        : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1)
                                + ' -- '
                                + lines[i + 1].replace(/^\s+/, ''));
        
        lines.splice(j, lines.length - j);
        return lines;
    },
    
    // Safari, Opera 9+, IE, and others
    fnRE = /function\s*([\w\-$]+)?\s*\(/i,
    maxStackSize = 10,
    _stringify = function (args) {
        var result = [];
        for (var i = 0; i < args.length; ++i) {
            var xs = args[i], x;
            if (typeof x == 'object')
                x = '#object';
            else if (typeof x == 'function')
                x = '#function';
            else if (typeof x == 'string')
                x = '"' + x + '"';
            xs.push(x)
        }
        return xs.join(',');
    },

    do_other = function (frame) {
        var stack = [],
            i = 0,
            fn, args;
    
        while (frame && stack.length < maxStackSize) {
            fn = fnRE.test(frame.toString()) ? RegExp.$1 || ANON : ANON;
            //args = Array.prototype.slice.call(frame['arguments']);
            stack[i++] = fn + '(' + _stringify(frame['arguments']) + ')';
            
            // Opera bug: if frame.caller does not exist, Opera returns frame (WTF)
            if (frame === frame.caller && window.opera)
                //TODO: check for same arguments if possible
                break;
            frame = frame.caller;
        }
        return stack;
    },
    
    
    _cache = {},
    _re_name = /function ([^(]*)\(([^)]*)\)/,
    _re_anon = /{anonymous}\(.*\)@(\w+:\/\/([-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/,
    _re_func = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,

    _get_source = function (url) {
        if (!(url in _cache))
            _cache[url] = get_url(url).split('\n');
        return _cache[url];
    },

    _guess_name = function (url, lineNo) {
        try {
            return _guess_name_from_line(lineNo, _get_source(url));
        } catch (e) {
            return 'getSource failed with url: ' + url + ', exception: ' + e;
        }
    },

    _guess_name_from_line = function (lineNo, source) {
        // Walk backwards from the first line in the function until we find the
        // line which matches the pattern above, which is the function
        // definition
        var line = '',
            maxLines = 10;
        for (var i = 0; i < maxLines; ++i) {
            line = source[lineNo - i] + line;
            if (line !== undefined) {
                var m = _re_func.exec(line);
                if (m)
                    return m[1];
                else
                    m = _re_name.exec(line);
                if (m && m[1])
                    return m[1];
            }
        }
        return '(?)';
    },

    guess_functions = function (stack) {
            for (var i = 0; i < stack.length; ++i) {
                var frame = stack[i],
                    m = _re_anon.exec(frame);
                if (m && m[1] && m[4])
                    stack[i] = frame.replace(ANON, _guess_name(m[1], m[4]));
            }
            return stack;
        }
    };

    return function (e, guess) {
        var result = (
            e.arguments ? do_chrome(e) :
            e.stack ? do_firefox(e) :
            window.opera && !('stacktrace' in e) ? do_opera(e) :
            do_other(arguments.callee)
        );
        return guess ? guess_functions(result) : result;
    }
    
})();

